﻿using Microsoft.Practices.ESB.Adapter;
using Microsoft.Practices.ESB.Cache;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.GlobalPropertyContext;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.Practices.ESB.Transform;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Streaming;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using System.Xml.XPath;

namespace BizTalk.ESB.Extensions.MessagingServices
{
    public class ItineraryServiceProxy : IMessagingService
    {
        private string _BizTalkSystemNamespace = @"http://schemas.microsoft.com/BizTalk/2003/system-properties";
        private string _CacheNamePrefix = @"ItineraryServiceProxy_";
        private Cache<string> EsbCache { get; set; }

        #region Itinerary Service Properties

        public string CorrelationPropertyName { get; set; }
        public string CorrelationPropertyNamespace { get; set; }
        public int CacheEntryTimeout { get; set; }

        #endregion

        public IBaseMessage Execute(IPipelineContext context, IBaseMessage msg, string resolverString, IItineraryStep step)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (msg == null)
                throw new ArgumentNullException("msg");
            if (string.IsNullOrEmpty(resolverString))
                throw new ArgumentException("Resolver must be provided", "resolverString");
            try
            {
                ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, resolverString);
                if (!info.Success)
                    throw new RoutingException("Invalid Resolver", resolverString);

                // Resolve configuration for routing.
                Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context);

                var maptype = resolverDictionary["Resolver.TransformType"];
                CacheEntryTimeout = int.Parse(resolverDictionary["Resolver.CacheTimeout"]);
                CorrelationPropertyName = resolverDictionary["Resovler.CorrelationPropertyName"];
                CorrelationPropertyNamespace = resolverDictionary["Resovler.CorrelationPropertyNamespace"];

                if (String.IsNullOrEmpty(maptype))
                {
                    return msg;
                }
                else
                {
                    if (msg.BodyPart != null)
                    {
                        var originalStrm = msg.BodyPart.GetOriginalDataStream();

                        if (originalStrm != null)
                        {
                            SaveMessageToCache(msg, context);
                        }
                    }

                    return msg;
                }
            }
            catch (System.Exception ex)
            {
                EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                throw;
            }
        }

        public string Name
        {
            get { return "BizTalk.ESB.Extensions.MessagingServices.ItineraryServiceProxy"; }
        }

        public bool ShouldAdvanceStep(IItineraryStep step, IBaseMessage msg)
        {
            return true;
        }

        public bool SupportsDisassemble
        {
            get { return false; }
        }

        #region Private Methods

        private void SaveMessageToCache(IBaseMessage message, IPipelineContext pContext)
        {
            bool serviceIsTwoWay;
            // Get the Message Content and Context (QUESTION: Cache just the message or the context too? What use is the context later on?)
            XmlDocument inputMsg = new XmlDocument();
            inputMsg.LoadXml(this.ReadMessageContent(pContext, message));
            string esbMessage = inputMsg.OuterXml;  // TODO: This is temporary...need to have some object to store the message AND the context

            // Save to Cache
            bool.TryParse(message.Context.Read(BtsProperties.IsRequestResponse.Name, BtsProperties.IsRequestResponse.Namespace).ToString(), out serviceIsTwoWay);
            string CorrelationId = message.Context.Read(CorrelationPropertyName, CorrelationPropertyNamespace).ToString();

            if (string.IsNullOrEmpty(CorrelationId))
            {
                throw new NotImplementedException("Itinerary Service not currently coded to handle lack of a correlation id");
            }
            else
            {
                this.EsbCache.Add(this._CacheNamePrefix + CorrelationId, esbMessage, new TimeSpan(0, 0, this.CacheEntryTimeout));
            }

        }

        private string ReadMessageContent(IPipelineContext pContext, IBaseMessage message)
        {
            //extract message
            var sbMessage = new StringBuilder(); //Save message in this StringBuilder
            var copyStream = new VirtualStream(); //Copy the original stream to the a new stream 
            var copyStreamWriter = new StreamWriter(copyStream); //Use a stream writer to copy original stream to copyStream
            ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(message.BodyPart.GetOriginalDataStream());

            //Read OriginalStream
            using (StreamReader streamReader = new StreamReader(seekableStream))
            {

                var line = string.Empty; //Read Stream Line by Line
                while ((line = streamReader.ReadLine()) != null)
                {
                    sbMessage.AppendLine(line); //Save Message to StringBuilder
                    copyStreamWriter.WriteLine(line); //Copy to new stream
                }

                copyStreamWriter.Flush(); //Ensure everything has been written to the new stream
                copyStream.Seek(0, SeekOrigin.Begin); //Set the new stream to the beginning
                message.BodyPart.Data = copyStream; //Set the Stream of the message leaving it's integrity intact

                pContext.ResourceTracker.AddResource(copyStream); //Ensure these resources are tracked and ultimately disposed
                pContext.ResourceTracker.AddResource(copyStreamWriter); //Ensure these resources are tracked and ultimately disposed

            }
            return sbMessage.ToString();
        }

        #endregion
    }
}
